package gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GraphicsConfiguration;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.HeadlessException;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.util.ArrayList;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.text.BadLocationException;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;

import org.math.plot.Plot2DPanel;

import chromosomes.Chromosome;
import chromosomes.GeneticAlgorithm;
import chromosomes.TypeSelection;

/**
 * 
 * @author Marco Gallardo Casu
 * @author Miguel Cisneros Rojas
 * 
 */
public class GUI extends JFrame {

	/**
	 * 
	 */
	private static final long serialVersionUID = -9217225855867375781L;
	private static final Color BACKGROUND = new Color(0xEEEEEE);
	private static final Color RED = new Color(0xEE2255);
	private static final Color WHITE = new Color(0xFFFFFF);
	private static final String[] LABELS = { "Population", "Generations",
			"Crossover probability", "Mutation probability" };
	private static final String[] TEXT_FIELDS = { "200", "1000", "0.7", "0.1" };
	private static final String[] EDITOR = { "Type selection:\n",
			"\nPopulation:\n", "\nGenerations:\n",
			"\nCrossover probability:\n", "\nMutation probability:\n",
			"\nFitness:\n" };
	private static final JComboBox<TypeSelection> cbTypeSelection = new JComboBox<TypeSelection>(
			TypeSelection.values());

	private static TypeSelection typeSelection = TypeSelection.ROULETTE;
	private static boolean elitism = false;
	private static JTextField[] textFields = new JTextField[TEXT_FIELDS.length];
	private static Plot2DPanel graph;
	private static JTextPane editor;

	private int population = 200;
	private int generations = 1000;
	private double crossover = 0.7;
	private double mutation = 0.1;
	private JFrame frame;
	private GeneticAlgorithm gA;

	public GUI() throws HeadlessException {
		super("Sudoku");
		init();
	}

	public GUI(GraphicsConfiguration arg0) {
		super("Sudoku", arg0);
		init();
	}

	public GUI(String arg0) throws HeadlessException {
		super(arg0);
		init();
	}

	public GUI(String arg0, GraphicsConfiguration arg1) {
		super(arg0, arg1);
		init();
	}

	private void init() {
		frame = this;
		for (int i = 0; i < TEXT_FIELDS.length; i++) {
			textFields[i] = new JTextField(TEXT_FIELDS[i]);
		}
		cbTypeSelection.setSelectedItem(TypeSelection.ROULETTE);
		createGUI("Multiplexor", null);
	}

	public JMenuBar getJMenuBar() {
		JMenuBar bar = new JMenuBar();

		JMenu file = new JMenu("File");
		JMenuItem exit = new JMenuItem("Exit");
		JMenu options = new JMenu("Options");
		JCheckBoxMenuItem elitism = new JCheckBoxMenuItem("Elitism");
		elitism.setSelected(GUI.elitism);

		bar.add(file);
		file.add(exit);
		bar.add(options);
		options.add(elitism);

		exit.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent arg0) {
				System.exit(0);
			}

		});

		elitism.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				GUI.elitism = ((JCheckBoxMenuItem) e.getSource()).isSelected();
			}

		});

		return bar;
	}

	private JTabbedPane getJTabbedPane(String title, Chromosome c) {
		JTabbedPane tabbedPane = new JTabbedPane();
		tabbedPane.addTab("Options", getPanelOptions());
		tabbedPane.addTab("Graph", getPanelGraph());
		tabbedPane.addTab("Statistics", getPanelStatistics(c));
		return tabbedPane;
	}

	private JPanel getPanelOptions() {
		JPanel panel = new JPanel(new GridBagLayout());
		JLabel[] labels = new JLabel[LABELS.length];

		GridBagConstraints constraints = new GridBagConstraints();
		constraints.gridx = 2;
		constraints.gridy = 0;
		constraints.anchor = GridBagConstraints.EAST;
		constraints.fill = GridBagConstraints.HORIZONTAL;
		panel.add(cbTypeSelection, constraints);

		for (int index = 0; index < LABELS.length; index++) {
			constraints = new GridBagConstraints();
			constraints.gridx = 0;
			constraints.gridy = index;
			constraints.anchor = GridBagConstraints.WEST;
			constraints.fill = GridBagConstraints.HORIZONTAL;
			constraints.insets = new Insets(2, 5, 4, 5);
			labels[index] = new JLabel(LABELS[index]);
			panel.add(labels[index], constraints);

			constraints = new GridBagConstraints();
			constraints.gridx = 1;
			constraints.gridy = index;
			constraints.anchor = GridBagConstraints.CENTER;
			constraints.fill = GridBagConstraints.HORIZONTAL;
			constraints.insets = new Insets(2, 5, 4, 5);
			constraints.ipadx = 50;
			panel.add(textFields[index], constraints);

			final int i = index;
			textFields[index].addFocusListener(new FocusListener() {

				@Override
				public void focusLost(FocusEvent arg0) {
					String s = textFields[i].getText();
					if (s.isEmpty()) {
						textFields[i].setBackground(WHITE);
					} else {
						if (i == 0 || i == 1) {
							if (isInteger(s) && Integer.valueOf(s) >= 0) {
								textFields[i].setBackground(WHITE);
							} else {
								textFields[i].setBackground(RED);
							}
						} else {
							if (isDouble(s) && Double.valueOf(s) >= 0) {
								textFields[i].setBackground(WHITE);
							} else {
								textFields[i].setBackground(RED);
							}
						}
					}
				}

				@Override
				public void focusGained(FocusEvent arg0) {
					textFields[i].setBackground(BACKGROUND);
				}

			});

		}

		constraints = new GridBagConstraints();
		constraints.gridx = 2;
		constraints.gridy = LABELS.length - 1;
		constraints.anchor = GridBagConstraints.EAST;
		constraints.fill = GridBagConstraints.HORIZONTAL;
		JButton button = new JButton("Resolve");
		panel.add(button, constraints);

		cbTypeSelection.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent arg0) {
				typeSelection = (TypeSelection) ((JComboBox<?>) arg0
						.getSource()).getSelectedItem();
			}

		});

		button.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent arg0) {
				resolve();
			}

		});

		return panel;
	}

	private Plot2DPanel getPanelGraph() {
		Plot2DPanel panel = new Plot2DPanel();
		panel.addLegend(Plot2DPanel.SOUTH);
		panel.setBorder(BorderFactory.createLoweredBevelBorder());
		return graph = panel;
	}

	private JPanel getPanelStatistics(Chromosome c) {
		JPanel panel = new JPanel(new BorderLayout());

		if (c == null) {
			editor = getTextEditor(EDITOR[0], "", EDITOR[1], "", EDITOR[2], "",
					EDITOR[3], "", EDITOR[4], "", EDITOR[5], "");
		} else {
			editor = getTextEditor(EDITOR[0], typeSelection.toString(),
					EDITOR[1], textFields[0].getText(), EDITOR[2],
					textFields[1].getText(), EDITOR[3],
					textFields[2].getText(), EDITOR[4],
					textFields[3].getText(), EDITOR[5],
					String.valueOf(c.getFitness()));
		}

		editor.setBorder(BorderFactory.createLoweredBevelBorder());
		panel.add(editor, BorderLayout.NORTH);

		return panel;
	}

	private JTextPane getTextEditor(String... ss) {

		JTextPane editor = new JTextPane();

		try {
			SimpleAttributeSet attrs = new SimpleAttributeSet();
			StyleConstants.setBold(attrs, true);

			boolean attr = true;
			for (String s : ss) {
				if (attr) {
					editor.getStyledDocument().insertString(
							editor.getStyledDocument().getLength(), s, attrs);
				} else {
					editor.getStyledDocument().insertString(
							editor.getStyledDocument().getLength(), s, null);
				}
				attr = !attr;
			}

		} catch (BadLocationException e) {
			System.err.println(e.getMessage());
		}

		return editor;
	}

	private int getPopulation() {
		return Integer.valueOf(textFields[0].getText());
	}

	private int getGenerations() {
		return Integer.valueOf(textFields[1].getText());
	}

	private double getCrossover() {
		return Double.valueOf(textFields[2].getText());
	}

	private double getMutation() {
		return Double.valueOf(textFields[3].getText());
	}

	private void createGUI(String title, Chromosome c) {
		frame.dispose();
		frame = new JFrame(title);
		frame.setJMenuBar(getJMenuBar());
		frame.setContentPane(getJTabbedPane(title, c));
		frame.pack();
		frame.setLocationRelativeTo(null);
		frame.setEnabled(true);
		frame.setResizable(false);
		frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
		frame.setVisible(true);
	}

	private void resolve() {
		final int GENERATIONS = getGenerations();
		if (isCorrect() && getGenerations() >= 0) {

			population = getPopulation();
			generations = getGenerations();
			crossover = getCrossover();
			mutation = getMutation();

			gA = new GeneticAlgorithm(population, crossover, mutation,
					typeSelection, elitism);

			long now = System.currentTimeMillis();
			Chromosome best = null;
			double[] average = null;
			double[] bestGen = null;
			double[] bestGlob = null;

			if (getGenerations() == 0) {
				ArrayList<Double> average1 = new ArrayList<Double>();
				ArrayList<Double> bestGen1 = new ArrayList<Double>();
				ArrayList<Double> bestGlob1 = new ArrayList<Double>();

				gA.cycle();
				bestGen1.add(gA.getBestFitness());
				average1.add(gA.getAverageGen());
				bestGlob1.add(gA.getBestFitness());
				best = gA.getBest();

				int cycle = 1;
				while (best.getFitness() < 243) {
					gA.cycle();
					bestGen1.add(gA.getBestFitness());
					average1.add(gA.getAverageGen());
					if (bestGen1.get(cycle) > bestGlob1.get(cycle - 1)) {
						bestGlob1.add(bestGen1.get(cycle));
						best = gA.getBest();
					} else {
						bestGlob1.add(bestGlob1.get(cycle - 1));
					}

					System.out.print("Gen: " + cycle + "\tBest Glob: "
							+ best.getFitness());
					System.out.print("\tBest Gen: " + bestGen1.get(cycle));
					System.out.println("\t\tAverage: " + average1.get(cycle));
					cycle++;
				}

				average = new double[average1.size()];
				bestGen = new double[bestGen1.size()];
				bestGlob = new double[bestGlob1.size()];

				for (int i = 0; i < average1.size(); i++) {
					average[i] = average1.get(i);
					bestGen[i] = bestGen1.get(i);
					bestGlob[i] = bestGlob1.get(i);
				}

			} else {
				average = new double[GENERATIONS];
				bestGen = new double[GENERATIONS];
				bestGlob = new double[GENERATIONS];

				gA.cycle();
				bestGen[0] = gA.getBestFitness();
				average[0] = gA.getAverageGen();
				bestGlob[0] = gA.getBestFitness();
				best = gA.getBest();

				for (int i = 1; i < getGenerations(); i++) {
					gA.cycle();
					bestGen[i] = gA.getBestFitness();
					average[i] = gA.getAverageGen();
					if (bestGen[i] > bestGlob[i - 1]) {
						bestGlob[i] = bestGen[i];
						best = gA.getBest();
					} else {
						bestGlob[i] = bestGlob[i - 1];
					}

					System.out.print("Gen: " + i + "\tBest Glob: "
							+ best.getFitness());
					System.out.print("\tBest Gen: " + bestGen[i]);
					System.out.println("\t\tAverage: " + average[i]);
				}
			}

			System.out.println("\nTiempo = "
					+ (System.currentTimeMillis() - (double) now) / 1000
					+ " s.");

			createGUI(getTitle(), best);
			textFields[0].setText(String.valueOf(population));
			textFields[1].setText(String.valueOf(generations));
			textFields[2].setText(String.valueOf(crossover));
			textFields[3].setText(String.valueOf(mutation));
			graph.removeAllPlots();
			graph.addLinePlot("Generational average", average);
			graph.addLinePlot("Generational  best", bestGen);
			graph.addLinePlot("Global best", bestGlob);
		}
	}

	private boolean isInteger(String s) {
		try {
			Integer.valueOf(s);
			return true;
		} catch (NumberFormatException e) {
			return false;
		}
	}

	private boolean isDouble(String s) {
		try {
			Double.valueOf(s);
			return true;
		} catch (NumberFormatException e) {
			return false;
		}
	}

	private boolean isCorrect() {
		return isInteger(textFields[0].getText())
				&& isInteger(textFields[1].getText())
				&& isDouble(textFields[2].getText())
				&& isDouble(textFields[3].getText());
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		new GUI();
	}

}
